const fs = require('fs');
const path = require('path');
const Conf = require('conf');
const semver = require('semver');
const format = require('string-template');
const axios = require('axios').default;
const { execSync } = require('child_process');
const conventionalChangelog = require('conventional-changelog');
const logger = require('./logger');

const config = new Conf({
  schema: {
    apiPrefix: {
      type: 'string',
      default: 'https://gitee.com/api/v5/',
    },
    locale: {
      type: 'string',
      default: process.env.LANG ? process.env.LANG.split('.')[0] : 'en_US',
    },
    accessToken: {
      type: 'string',
    },
  },
});

const locales = {
  en_US: {
    fix: 'Bug Fixes',
    feat: 'Features',
    perf: 'Performance Improvements',
    breaking_changes: 'BREAKING CHANGES',
    first_release: 'First release.',
    no_description: 'No description.',
    title: '{name} {version} released, {summary}',
    summary: {
      regular: 'regular updates',
      many_bug_fixes: 'many bug fixes',
      many_features: 'many features',
      breaking_changes: 'breaking changes',
      performance_improvements: 'performance improvements',
    },
  },
  zh_CN: {
    fix: 'Bug 修复',
    feat: '新特性',
    perf: '性能优化',
    breaking_changes: '重要改动',
    first_release: '第一个发行版。',
    no_description: '无描述。',
    title: '{name} {version} 发布, {summary}',
    summary: {
      regular: '常规更新',
      many_bug_fixes: '大量 bug 修复',
      many_features: '大量新特性',
      breaking_changes: '重要改动',
      performance_improvements: '性能优化',
    },
  },
};

function getTranslation() {
  let locale = config.get('locale');

  if (!(locale in locales)) {
    locale = 'en_US';
  }
  return locales[locale];
}

if (!fs.existsSync(path.join(process.cwd(), 'package.json'))) {
  logger.error('package.json does not exist');
  process.exit(1);
}
const PKG = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json')));
const INITIAL_VERSION = '1.0.0';
const TAG_PREFIX = 'v';

function getReleaseUrl(info) {
  return `${config.get('apiPrefix')}repos/${info.owner}/${info.repo}/releases`;
}

function getReleaseParams(params) {
  if (!config.get('accessToken')) {
    throw new Error('access token does not exist');
  }
  return {
    access_token: config.get('accessToken'),
    ...params,
  };
}

async function fetchLatestVersion(info) {
  const url = `${getReleaseUrl(info)}/latest`;

  logger.log(`Fetching latest release from ${url}`);

  const release = await new Promise((resolve) => {
    axios.get(url, { params: getReleaseParams() }).then((res) => {
      resolve(res.data);
    }, () => {
      logger.warning('Unable to get latest release');
      resolve({});
    });
  });
  if (release.tag_name && release.tag_name.charAt(0) === 'v') {
    const version = release.tag_name.slice(1);
    logger.log(`Latest version: ${version}`);
    return [version, release.target_commitish];
  }
  return [];
}

async function generateReleaseNote(latestVersion, commitId) {
  let note = '';
  const gitOptions = {
    from: commitId,
  };

  if (!latestVersion) {
    return '';
  }
  logger.log(`Analyzing commit history from ${latestVersion}`);
  await new Promise((resolve, reject) => {
    conventionalChangelog(
      { preset: 'angular' },
      { version: '{version}' },
      gitOptions,
      null,
      {
        linkReferences: false,
        commitsSort: 'header',
      },
    )
      .on('data', (data) => {
        note += data.toString();
      })
      .on('error', reject)
      .on('end', resolve);
  });
  return note.trim();
}

function getNextVersion({ changes, latestVersion, prerelease }) {
  let version = latestVersion;
  const prereleaseId = typeof prerelease === 'string' ? prerelease : 'beta';

  if (!latestVersion) {
    if (prerelease) {
      if (typeof prerelease === 'string') {
        return semver(`${INITIAL_VERSION}-${prerelease}`);
      }
      return semver(`${INITIAL_VERSION}-beta`);
    }
    return semver(INITIAL_VERSION);
  }
  if (semver(version).prerelease.length > 0 && prerelease) {
    version = semver.inc(version, 'prerelease', prereleaseId);
  } else {
    if (changes.breakingChanges) {
      version = semver.inc(version, 'major');
    } else if (changes.features) {
      version = semver.inc(version, 'minor');
    } else {
      version = semver.inc(version, 'patch');
    }
    if (prerelease) {
      version += `-${prereleaseId}`;
    }
  }
  return version;
}

function getGitRepoInfo() {
  const info = { repo: 'unknown', owner: 'unknown' };
  const repo = PKG.repository;

  function parseUrl(url) {
    [info.owner, info.repo] = url.split(':')[1].split('/');
    if (info.repo.endsWith('.git')) {
      info.repo = info.repo.slice(0, -4);
    }
  }

  if (repo && repo.url && repo.type === 'git') {
    parseUrl(repo.url);
  } else {
    try {
      parseUrl(execSync('git remote get-url origin', { encoding: 'utf-8' }).trim());
    } catch (_err) {
      return info;
    }
  }
  return info;
}

async function collect(latestVersion) {
  let commitId;
  let version = latestVersion;
  const info = getGitRepoInfo();

  if (version) {
    commitId = `${TAG_PREFIX}${version}`;
  } else {
    [version, commitId] = await fetchLatestVersion(info);
  }

  const note = await generateReleaseNote(version, commitId);
  const messages = new Set();
  const changes = {
    bugs: 0,
    features: 0,
    breakingChanges: 0,
    performanceImprovements: 0,
  };
  let changesKey = null;

  const lines = note.split('\n').filter((line) => {
    if (!line) {
      return true;
    }
    if (changesKey && line.startsWith('*')) {
      if (changes[changesKey] >= 100) {
        return false;
      }

      let message = line.slice(1);
      const i = message.indexOf('(');

      if (i > 0) {
        message = message.substring(0, i).trim();
      }
      // 忽略重复的提交信息
      if (messages.has(message)) {
        return false;
      }
      changes[changesKey] += 1;
      messages.add(message);
    } else if (line.includes('BREAKING CHANGES')) {
      changesKey = 'breakingChanges';
    } else if (line.includes('Features')) {
      changesKey = 'features';
    } else if (line.includes('Bug Fixes')) {
      changesKey = 'bugs';
    } else if (line.includes('Performance Improvements')) {
      changesKey = 'performanceImprovements';
    }
    return true;
  });
  return {
    changes, note: lines.join('\n'), latestVersion: version, ...info,
  };
}

function generate({
  prerelease = false, changes, note, version, owner, repo,
} = {}) {
  const info = {
    tag_name: INITIAL_VERSION,
    name: '',
    body: note,
    version,
    owner,
    repo,
    prerelease: !!prerelease,
    target_commitish: 'master',
  };
  const t = getTranslation();

  if (info.body.trim().length < 2) {
    if (info.version === `v${INITIAL_VERSION}`) {
      info.body += `\n\n${t.first_release}`;
    } else {
      info.body += `\n\n${t.no_description}`;
    }
  }

  let summary = t.summary.regular;
  const changesCount = changes.bugs + changes.features;

  if (changes.breakingChanges > 0) {
    summary = t.summary.breaking_changes;
  } else if (changes.performanceImprovements > 4
    && changes.performanceImprovements > changesCount * 0.2) {
    summary = t.summary.performance_improvements;
  } else if (changes.features > 10 && changes.features > changesCount * 0.4) {
    summary = t.summary.many_features;
  } else if (changes.bugs > 10 && changes.bugs && changesCount * 0.4) {
    summary = t.summary.many_bug_fixes;
  }
  info.tag_name = `${TAG_PREFIX}${info.version}`;
  info.name = format(t.title, { name: PKG.name, version: info.version, summary });
  info.body = format(info.body, { version: info.version })
    .replace('{version}', info.version)
    .replace('BREAKING CHANGES', t.breaking_changes)
    .replace('Features', t.feat)
    .replace('Bug Fixes', t.fix)
    .replace('Performance Improvements', t.perf);
  return info;
}

async function upload(info) {
  let response;
  const url = getReleaseUrl(info);
  const htmlUrl = `https://gitee.com/${info.owner}/${info.repo}/releases/${info.tag_name}`;
  const params = getReleaseParams(info);

  delete params.repo;
  delete params.owner;
  delete params.version;
  logger.log(`uploading release to ${url}`);
  return new Promise((resolve) => {
    axios.post(url, params).then(
      (res) => {
        logger.log(`the release has been uploaded, url: ${htmlUrl}`);
        resolve(res.data);
      },
      (err) => {
        response = err.response;
        if (response && response.data.message) {
          logger.error(response.data.message);
        } else {
          logger.error(err.message);
        }
      },
    );
  });
}

module.exports = {
  config,
  collect,
  getNextVersion,
  generate,
  upload,
};
